#include "os/os.h"
#include "bsp/cpu_driver.h"
#include "utils/utils.h"
#include "utils/circle_buffer.h"
#include "communication/message.h"

#define UART_BAUDRATE				    500000

#define UART_com					USART2
#define UART_tx_port				GPIOA
#define UART_tx_pin				GPIO_PINS_2
#define UART_rx_port				GPIOA
#define UART_rx_pin				GPIO_PINS_3
#define UART_irq					USART2_IRQn
#define UART_clk					CRM_USART2_PERIPH_CLOCK
#define UART_tx_gpio_clk			CRM_GPIOA_PERIPH_CLOCK
#define UART_rx_gpio_clk			CRM_GPIOA_PERIPH_CLOCK
#define UART_tx_dma				DMA1
#define UART_tx_dma_ch			DMA1_CHANNEL7
#define UART_tx_dma_clk			CRM_DMA1_PERIPH_CLOCK
#define UART_rx_dma				DMA1
#define UART_rx_dma_ch			DMA1_CHANNEL6
#define UART_rx_dma_clk			CRM_DMA1_PERIPH_CLOCK
#define UART_DMA_TX_IRQ          DMA1_Channel7_IRQn
#define UART_DMA_IRQHandler             DMA1_Channel7_IRQHandler

#define DMA_CHCTL(dma, dma_ch) ((dma_channel_type *)dma_ch)->ctrl
#define DMA_CHMADDR(dma, dma_ch) ((dma_channel_type *)dma_ch)->maddr
#define DMA_CHCNT_RX(dma, dma_ch) ((dma_channel_type *)dma_ch)->dtcnt

#define DMA_CHXCTL_CHEN        BIT(0)                  /*!< channel enable */

// ================================================================================

static hw_call_back rx_cb_;
static void* ctx_;
static c_buffer_t *tx_queue_;
static u8 rx_cache[CONFIG_UART_RX_BUFFER_SIZE];
static u16 tx_length, rx_index;

static void uart_pin_init(void){
	crm_periph_clock_enable(UART_rx_gpio_clk, TRUE);
	crm_periph_clock_enable(UART_tx_gpio_clk, TRUE);

	gpio_init_type gpio_init_struct;
	gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
	gpio_init_struct.gpio_out_type	= GPIO_OUTPUT_PUSH_PULL;
	gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
	gpio_init_struct.gpio_pins = UART_tx_pin;
	gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
	gpio_init(UART_tx_port, &gpio_init_struct);
	
	/* configure the usart2 rx pin */
	gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;
	gpio_init_struct.gpio_pins = UART_rx_pin;
	gpio_init_struct.gpio_pull = GPIO_PULL_UP;
	gpio_init(UART_rx_port, &gpio_init_struct);
}

static void uart_device_init(void){
	crm_periph_clock_enable(UART_clk, TRUE);
	usart_reset(UART_com);
	/* configure usart2 param */
	usart_init(UART_com, UART_BAUDRATE, USART_DATA_8BITS, USART_STOP_1_BIT);
	usart_transmitter_enable(UART_com, TRUE);
	usart_receiver_enable(UART_com, TRUE);
	usart_enable(UART_com, TRUE);
}

static void uart_tx_dma_init(void){
	dma_init_type dma_init_struct;
	crm_periph_clock_enable(UART_tx_dma_clk, TRUE);
	
	dma_reset(UART_tx_dma_ch);
	dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;
	dma_init_struct.memory_inc_enable = TRUE;
	dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
	dma_init_struct.peripheral_base_addr = (u32) &(((usart_type *)UART_com)->dt);
	dma_init_struct.peripheral_inc_enable = FALSE;
	dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
	dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
	dma_init_struct.loop_mode_enable = FALSE;
	dma_init(UART_tx_dma_ch, &dma_init_struct);
	/* config flexible dma for usart2 tx */
	//dma_flexible_config(DMA1, FLEX_CHANNEL1, DMA_FLEXIBLE_UART2_TX);

	usart_dma_transmitter_enable(UART_com, TRUE);
}

static void uart_rx_dma_init(void){
	dma_init_type dma_init_struct;
	crm_periph_clock_enable(UART_rx_dma_clk, TRUE);
	/* dma1 channel2 for usart2 rx configuration */
	dma_reset(UART_rx_dma_ch);
	dma_default_para_init(&dma_init_struct);
	dma_init_struct.buffer_size = CONFIG_UART_RX_BUFFER_SIZE;
	dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY;
	dma_init_struct.memory_base_addr = (uint32_t)rx_cache;
	dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;
	dma_init_struct.memory_inc_enable = TRUE;
	dma_init_struct.peripheral_base_addr = (u32) &(((usart_type *)UART_com)->dt);
	dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;
	dma_init_struct.peripheral_inc_enable = FALSE;
	dma_init_struct.priority = DMA_PRIORITY_MEDIUM;
	dma_init_struct.loop_mode_enable = FALSE;
	dma_init(UART_rx_dma_ch, &dma_init_struct);	
	/* config flexible dma for usart2 rx */
	//dma_flexible_config(DMA1, FLEX_CHANNEL2, DMA_FLEXIBLE_UART2_RX);

	usart_dma_receiver_enable(UART_com, TRUE);
}


void uart_set_dma_queue(unsigned char hw_id, void *queue) {
	tx_queue_ = (c_buffer_t *)queue;
}


void uart_init(unsigned char hw_id, unsigned int rate, hw_call_back cb, void *ctx) {
	rx_cb_ = cb;
	ctx_ = ctx;
	uart_pin_init();
	uart_device_init();
	uart_tx_dma_init();
	uart_rx_dma_init();
	usart_enable(UART_com, TRUE);
}

void uart_tx_poll(unsigned char hw_id)
{
	u32 value = DMA_CHCTL(UART_tx_dma, UART_tx_dma_ch);

	if (value & DMA_CHXCTL_CHEN) {
		if (SET != dma_flag_get(DMA1_FDT1_FLAG)) {
			return;
		}
		dma_flag_clear(DMA1_FDT1_FLAG);
		circle_skip_read_size(tx_queue_, tx_length);
		DMA_CHCTL(UART_tx_dma, UART_tx_dma_ch) = value & (~DMA_CHXCTL_CHEN);
	}

	tx_length = circle_get_read_size(tx_queue_);
	if (tx_length > 0) {
		dma_data_number_set(UART_tx_dma_ch,  tx_length);
		DMA_CHMADDR(UART_tx_dma, UART_tx_dma_ch) = (u32) circle_get_read_head(tx_queue_);
		DMA_CHCTL(UART_tx_dma, UART_tx_dma_ch) = value | DMA_CHXCTL_CHEN;
	}
}

void uart_rx_poll(unsigned char hw_id)
{
	u16 index = rx_index;

	rx_index = sizeof(rx_cache) - DMA_CHCNT_RX(UART_rx_dma, UART_rx_dma_ch);
	if (rx_cb_) {
		if (rx_index < index) {
			rx_cb_(ctx_, rx_cache + index, sizeof(rx_cache) - index);
			rx_cb_(ctx_, rx_cache, rx_index);
		} else if (rx_index > index){
			rx_cb_(ctx_, rx_cache + index, rx_index - index);
		}
	}
}

